#pragma once

#include <task.h>

#include "os_basic_common.hpp"

#ifdef ARDUINO
    #include <Arduino.h>
#endif


namespace os_basic {

    namespace hide {

        template <typename T>
        class TimeSourceBase {
           public:
            using TimeType = T;

           protected:
            TimeType _last_time;
        };

    }  // namespace hide


    class OsTickSource : public hide::TimeSourceBase<TickType_t> {
       public:
        OsTickSource() {
            reset();
        }

        TimeType now() {
            return xTaskGetTickCount();
        }

        TimeType diff() {
            return now() - _last_time;
        }

        void reset() {
            _last_time = now();
        }
    };


    struct OsMsSource : public hide::TimeSourceBase<decltype(tick_to_ms(1))> {
       private:
        uint32_t _ticks_per_milli_second = 1;

       public:
        OsMsSource() {
            reset();
        }

        TimeType now() {
            return tick_to_ms(xTaskGetTickCount());
        }

        /**
         * @brief 返回毫秒时间差，精度不低于now()。
         *
         * now() 基于freertos 的系统tick，如果系统tick 周期是16ms，
         * now() 的分辨率就只有16ms。
         *
         * diff() 会尽可能做一些别的计算以提高精度。比如：在arduino 环境下，调用millis() 获取时间；
         * 在Cortex-M 环境下，根据SysTick 的重装值LOAD 和当前值VAL，计算出高精度时间。
         * 这些额外的步骤可能需要消耗更多计算资源。
         *
         * @param last_time   第一次调用时，可将last_time 设为0，每次调用time_diff 会自动更新last_time 的值
         * @return TimeType
         */
        TimeType diff() {
            //
#if defined(ARDUINO)
            auto now = millis();
            auto diff = now - _last_time;
            _last_time = now;
            return diff;

#elif defined(SysTick) && portTICK_PERIOD_MS > 1
            // 根据SysTick 寄存器的当前值计算时间
            auto ms = tick_to_ms(xTaskGetTickCount());
            auto v = SysTick->VAL;

            auto ticks_per_cycle = SysTick->LOAD + 1;
            ms += (ticks_per_cycle - v) / _ticks_per_milli_second;

            auto temp_last_time = last_time;

            // 这部分代码用来避免SysTick->VAL 重置导致计算结果异常
            // 如果last_time 大于当前计算得出的当前时间ms，有可能是因为ms 计数溢出，
            // 比如，last_time 最大值是9，ms 计算结果是11，溢出变成1。
            // 这种情况下，只要上一次调用time_diff 的时间不是特别久远，
            // ms 溢出时，last_time 肯定也接近溢出，last_time 减去ms 的结果应该是个比较大的值，接近ms 的最大值9。
            // 
            // 另一种情况是SysTick->VAL 溢出归零，此时ms 也会算的比较小，
            // 比如，Systick 溢出周期是10ms，即portTICK_PERIOD_MS == 10，
            // 假设tick_to_ms 得到的结果是90，物理时间从90 到100 增加的过程中，调用了一次time_diff
            // 计算得到96，并存储为last_time。
            // 下一次调用time_diff 时，Systick->VAL 溢出归零，而全局的毫秒计数器还没更新，值是90，
            // 于是计算的ms 变成90，比96 小。
            // 这种情况下，last_time 减去ms 的结果是个比较小的值，不超过一个溢出周期10。
            //
            // 这行代码就利用这个条件判断出第二种情况，避免SysTick 归零导致计数BUG。
            if (temp_last_time > ms && (temp_last_time - ms) < portTICK_PERIOD_MS) {
                ms = _last_time;
            }
            else {
                _last_time = ms;
            }

            auto diff = ms - temp_last_time;
            return diff;

#else
            auto now = tick_to_ms(xTaskGetTickCount());
            auto diff = now - _last_time;
            _last_time = now;
            return diff;

#endif
        }

        void reset() {
#if defined(SysTick) && portTICK_PERIOD_MS > 1
            uint32_t ticks_per_cycle = SysTick->LOAD + 1;
            uint32_t _ticks_per_milli_second = ticks_per_cycle / portTICK_PERIOD_MS;
#endif
            _last_time = now();
        }
    };


}  // namespace os_basic